﻿// <copyright file="TagXmlHelper.cs" company="Signal Processing Lab, Wuhan University">
// Copyright (c) 2009-2010 All Right Reserved
// </copyright>
// <author>Xin-ping Deng</author>
// <email>dentoby@gmail.com</email>
// <date>2010-4</date>
// <summary>This is part of Image.NET project.</summary>

namespace SPLWHU.Core.Tags
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;

    /// <summary>
    /// The helper class to convert a xml document to a <see cref="Tag"/> or the reverse.
    /// </summary>
    public class TagXmlHelper
    {
        /// <summary>
        /// The tag creating implement.
        /// </summary>
        private delegate Tag CreateTagImp(string name);

        /// <summary>
        /// The tag creators.
        /// </summary>
        private static Dictionary<string, CreateTagImp> CreateTagImplements = new Dictionary<string, CreateTagImp>();

        /// <summary>
        /// Initializes the <see cref="TagXmlHelper"/> class.
        /// </summary>
        static TagXmlHelper()
        {
            CreateTagImplements.Add(typeof(bool).ToString(), new CreateTagImp(CreateBoolTag));
            CreateTagImplements.Add(typeof(int).ToString(), new CreateTagImp(CreateIntTag));
            CreateTagImplements.Add(typeof(double).ToString(), new CreateTagImp(CreateDoubleTag));
            CreateTagImplements.Add(typeof(string).ToString(), new CreateTagImp(CreateStringTag));
            CreateTagImplements.Add(typeof(TagCollection).ToString(), new CreateTagImp(CreateCollection));
        }

        /// <summary>
        /// Converts a <see cref="Tag"/> to a XML document.
        /// </summary>
        /// <param name="tag">The tag to convert.</param>
        /// <param name="filename">The filename of the XML document.</param>
        public static void TagToXml(Tag tag, string filename)
        {
            // Creates the writer.
            XmlNodeWriter writer = new XmlNodeWriter(tag.Name);

            // Writes the tag information.
            WriterTagInfo(tag, writer);

            // Saves to file.
            writer.SaveToFile(filename);
        }

        /// <summary>
        /// Writers the information such as Type, Read Only and so on.
        /// </summary>
        /// <param name="tag">The tag to write.</param>
        /// <param name="writer">The XML node writer.</param>
        private static void WriterTagInfo(Tag tag, XmlNodeWriter writer)
        {
            // Writes the type and [read only] attributes.
            writer.AddAttribute("Type", tag.ValueType.ToString());
            writer.AddAttribute("ReadOnly", tag.ReadOnly.ToString());

            if (tag is TagCollection)
            {
                // The parent node.
                TagCollection p = tag as TagCollection;

                // Appends the child nodes.
                foreach (Tag c in p.Enumerate())
                {
                    XmlNodeWriter w = writer.CreateChild(c.Name);
                    WriterTagInfo(c, w);
                }
            }
            else
            {
                // Writes the value.
                writer.SetValue(tag.ValueToText());
            }
        }

        /// <summary>
        /// Converts a XML document to a <see cref="Tag"/>.
        /// </summary>
        /// <param name="filename">The filename of a XML document.</param>
        /// <returns>The <see cref="Tag"/> created from the XML document.</returns>
        public static Tag XmlToTag(string filename)
        {
            // Creates the reader.
            XmlNodeReader reader = new XmlNodeReader(filename);

            // Reads the tag information.
            return ReadTagInfo(reader);
        }

        /// <summary>
        /// Reads the information of a <see cref="Tag"/>.
        /// </summary>
        /// <param name="reader">The reader.</param>
        /// <returns>The <see cref="Tag"/> created.</returns>
        private static Tag ReadTagInfo(XmlNodeReader reader)
        {
            // Reads the name and the data type.
            string name = reader.Name;
            string type = string.Empty;
            reader.GetAttribute("Type", ref type);

            // Creates the tag.
            Tag ret = CreateTag(name, type);
            if (ret is TagCollection)
            {
                int cnt = reader.ChildCount;
                for (int i = 0; i < cnt; i++)
                {
                    // Adds the inner tags.
                    XmlNodeReader r = reader.ReadChildNode(i);
                    Tag c = ReadTagInfo(r);
                    ((TagCollection)ret).Add(c);
                }
            }
            else
            {
                // Sets the value.
                ret.ValueFromText(reader.Value);
            }

            return ret;
        }

        #region Tag Creating methods

        /// <summary>
        /// Creates a <see cref="Tag"/> by specifying the name and data type.
        /// </summary>
        /// <param name="type">The data type.</param>
        /// <param name="name">The name of the tag.</param>
        /// <returns></returns>
        private static Tag CreateTag(string type, string name)
        {
            if (CreateTagImplements.ContainsKey(type))
            {
                // The known creators.
                return CreateTagImplements[type](name);
            }
            else
            {
                // The value type and tag type.
                Type tagType, valType = Type.GetType(type);

                if (valType == null)
                {
                    throw new Exception("Invlid value type: " + valType.ToString());
                }

                if (valType.IsEnum)
                {
                    // The enum tag.
                    tagType = typeof(EnumTag<>).MakeGenericType(valType);
                }
                else
                {
                    // The generic tag.
                    tagType = typeof(Tag<>).MakeGenericType(valType);
                }

                // Gets the contructor.
                System.Reflection.ConstructorInfo con = tagType.GetConstructor(new Type[] { typeof(string) });
                if (con == null)
                {
                    throw new Exception("Invalid tag type: " + tagType.ToString());
                }

                // Creates the tag.
                return (Tag)con.Invoke(new object[] { name });
            }
        }

        /// <summary>
        /// Creates a <see cref="BoolTag"/>.
        /// </summary>
        /// <param name="name">The name of the tag.</param>
        /// <returns>The tag created.</returns>
        private static Tag CreateBoolTag(string name)
        {
            return new BoolTag(name);
        }

        /// <summary>
        /// Creates a <see cref="IntTag"/>.
        /// </summary>
        /// <param name="name">The name of the tag.</param>
        /// <returns>The tag created.</returns>
        private static Tag CreateIntTag(string name)
        {
            return new IntTag(name);
        }

        /// <summary>
        /// Creates a <see cref="DoubleTag"/>.
        /// </summary>
        /// <param name="name">The name of the tag.</param>
        /// <returns>The tag created.</returns>
        private static Tag CreateDoubleTag(string name)
        {
            return new DoubleTag(name);
        }

        /// <summary>
        /// Creates a <see cref="StringTag"/>.
        /// </summary>
        /// <param name="name">The name of the tag.</param>
        /// <returns>The tag created.</returns>
        private static Tag CreateStringTag(string name)
        {
            return new StringTag(name);
        }

        /// <summary>
        /// Creates a <see cref="TagCollection"/>.
        /// </summary>
        /// <param name="name">The name of the tag.</param>
        /// <returns>The tag created.</returns>
        private static Tag CreateCollection(string name)
        {
            return new TagCollection(name);
        }

        #endregion
    }
}
